<?php

namespace Plugins\AdminBasic\Http\Admin\Service;

use App\Exception\ApiMessageException;
use App\Models\Model;
use App\Util\ApiAuth;
use App\Util\Helper;
use App\Util\ImitateAop\Attributes\Transaction;
use Hyperf\HttpServer\Annotation\GetMapping;
use Hyperf\HttpServer\Annotation\Middlewares;
use Hyperf\HttpServer\Annotation\PostMapping;
use Hyperf\Stringable\Str;
use Hyperf\Validation\Annotation\Scene;
use Hyperf\Validation\Middleware\ValidationMiddleware;
use Plugins\ApiDoc\Attributes\Api;
use Plugins\ApiDoc\Attributes\ApiBody;
use Plugins\ApiDoc\Attributes\ApiQuery;
use Plugins\ApiDoc\Attributes\ApiReturn;
use Psr\Http\Message\ResponseInterface;
use Sc\Util\ClassFile\ClassFileResolve;
use Sc\Util\ClassFile\Components\Attribute;
use Sc\Util\ClassFile\Components\ClassFileConstruction;
use Sc\Util\ClassFile\Components\FunctionParam;
use Sc\Util\ClassFile\Components\Method;
use Sc\Util\ImitateAopProxy\AopProxyTrait;
use Sc\Util\ScTool;
use Sc\Util\Tool\Dir\EachFile;

/**
 * Class ApiMakeService
 */
class ApiMakeService
{
    use AopProxyTrait;

    /**
     * @param string $module
     *
     * @return array
     * @throws \Exception
     */
    public function ApiModuleFileQuery(string $module): array
    {
        $controllerPath = BASE_PATH . '/app/Http/' . $module . '/Controller';
        $servicePath    = BASE_PATH . '/app/Http/' . $module . '/Service';
        $controllers    = ScTool::dir($controllerPath)->getAllFiles() ?: [];
        $service        = ScTool::dir($servicePath)->getAllFiles() ?: [];

        $this->fileSort($controllers);
        $this->fileSort($service);

        return [
            [
                'filename' => 'Controller',
                'path'     => $controllerPath,
                'type'     => 'dir',
                'children' => $controllers
            ],
            [
                'filename' => 'Service',
                'path'     => $servicePath,
                'type'     => 'dir',
                'children' => $service
            ]
        ];
    }


    private function fileSort(&$data): void
    {
        usort($data, function ($a, $b) {
            if ($a['type'] == $b['type']) {
                return $a['filename'] <=> $b['filename'];
            }

            return $a['type'] == 'dir' ? -1 : 1;
        });

        foreach ($data as &$datum) {
            if ($datum['type'] == 'dir' && $datum['children']) {
                $this->fileSort($datum['children']);
            }
        }
    }

    /**
     * @param string $filepath
     * @param array  $update
     *
     * @return array
     * @throws ApiMessageException
     */
    public function getFileContent(string $filepath, array $update): array
    {
        if ($filepath) {
            $fileContent = file_get_contents($filepath);
            $resolveFileContent = $this->afterUpdateContent($filepath, $fileContent, $update);
        }else{
            if (!empty($update['controller_filepath'])) {
                $fileContent = file_get_contents($update['controller_filepath']);
                $resolveFileContent = $this->afterUpdateContent($update['controller_filepath'], $fileContent, $update);
                file_put_contents($update['controller_filepath'], $resolveFileContent);
            }

            if (!empty($update['service_filepath'])) {
                $fileContent = file_get_contents($update['service_filepath']);
                $resolveFileContent = $this->afterUpdateContent($update['service_filepath'], $fileContent, $update);
                file_put_contents($update['service_filepath'], $resolveFileContent);
            }
        }

        return [
            'origin'  => $fileContent ?? '',
            'resolve' => $resolveFileContent ?? ''
        ];
    }

    private function makeApiService(ClassFileConstruction $classFileConstruction, array $data, string $model): Method
    {
        if ($classFileConstruction->getClassMethod($data['method'])){
            throw new ApiMessageException($classFileConstruction->getName() . "::{$data['method']} 已存在");
        }

        $method = new Method($data['method']);
        if ($data['function'] == 'single') {
            $functionParam = new FunctionParam('id');
            $functionParam->setType('int');
            $method->addParameters($functionParam);
            $method->setReturnType('array');

            $fields = implode("','", $data['fields']);
            $method->addCode("\$data = $model::find(\$id);");
            $method->addCode("\$data->setVisible(['{$fields}']);");
            $method->addCode("return \$data->toArray();");

            $classFileConstruction->addMethods($method);
            return $method;
        }elseif ($data['function'] == 'multiple'){
            $pageParam = new FunctionParam('page');
            $pageParam->setType('int');
            $limitParam = new FunctionParam('limit');
            $limitParam->setType('int');
            $method->addParameters($pageParam);
            $method->addParameters($limitParam);
            $method->setReturnType('array');

            $table = Str::snake(ClassFileConstruction::getClassShortName($model));
            $fields = implode("', '", array_map(fn($f) => "$table.$f", $data['fields']));
            $method->addCode("\$data = $model::forPage(\$page, \$limit);");
            $method->addCode("\$data->select(['{$fields}']);");
            $method->addCode("return \$data->get()->toArray();");
        }elseif ($data['function'] == 'save'){
            $dataParam = new FunctionParam('data');
            $dataParam->setType('array');
            $method->addParameters($dataParam);
            $userIdParam = new FunctionParam('userId');
            $userIdParam->setType('int');
            $method->addParameters($userIdParam);

            $transaction = new Attribute($classFileConstruction->getAppropriateClassName(Transaction::class));
            $method->addAttribute($transaction);
            $method->addCode("$model::create(\$data);");
        }

        $classFileConstruction->addMethods($method);

        return $method;
    }

    public function makeApiController(ClassFileConstruction $classFileConstruction, array $data, string $model): Method
    {
        if ($classFileConstruction->getClassMethod($data['method'])){
            throw new ApiMessageException($classFileConstruction->getName() . "::{$data['method']} 已存在");
        }

        $method = new Method($data['method']);
        preg_match('/^namespace\s+(.*);/m', file_get_contents($data['service_filepath']), $match);
        $service = $match[1] . '\\' . basename($data['service_filepath'], '.php');

        $method->setReturnType(ResponseInterface::class, $classFileConstruction);
        $method->setDocBlockComment([
            'single' == $data['function'] ? '详情' : '列表',
            '',
            '@return ResponseInterface',
        ]);

        $method->addAttribute(new Attribute($classFileConstruction->getAppropriateClassName(Api::class)));
        $apiQuery  = $classFileConstruction->getAppropriateClassName(ApiQuery::class);
        $apiBody   = $classFileConstruction->getAppropriateClassName(ApiBody::class);
        $apiReturn = $classFileConstruction->getAppropriateClassName(ApiReturn::class);

        if ('single' == $data['function']) {
            $idAttribute = new Attribute($apiQuery);
            $idAttribute->addParam('id');
            $idAttribute->addParam('Integer');
            $idAttribute->addParam('ID');
            $method->addAttribute($idAttribute);
        }else if ('multiple' == $data['function']){
            $pageAttribute = new Attribute($apiQuery);
            $pageAttribute->addParam('page');
            $pageAttribute->addParam('Integer');
            $pageAttribute->addParam('页码');
            $method->addAttribute($pageAttribute);

            $limitAttribute = new Attribute($apiQuery);
            $limitAttribute->addParam('pageSize');
            $limitAttribute->addParam('Integer');
            $limitAttribute->addParam('每页数量');
            $method->addAttribute($limitAttribute);
        }

        foreach ($data['fields_info'] as $field) {
            $attributeType = 'save' == $data['function'] ? $apiBody : $apiReturn;
            $attribute  = new Attribute($attributeType);
            $attribute->addParam($field['COLUMN_NAME']);
            $attribute->addParam(match($field['DATA_TYPE']){
                'int', 'tinyint', 'smallint', 'mediumint', 'bigint' => 'Integer',
                'float', 'double', 'decimal' => 'Float',
                'json' => 'Array',
                default => 'String'
            });
            $attribute->addParam($field['COLUMN_COMMENT']);
            $method->addAttribute($attribute);

            if (str_contains($field['COLUMN_NAME'], 'image') && ($field['DATA_TYPE'] == 'json' || $field['DATA_TYPE'] == 'text')) {
                $attribute = new Attribute($attributeType);
                $attribute->addParam($field['COLUMN_NAME'] . '.url');
                $attribute->addParam('String');
                $attribute->addParam('图片地址');
                $method->addAttribute($attribute);
                $attribute = new Attribute($attributeType);
                $attribute->addParam($field['COLUMN_NAME'] . '.name');
                $attribute->addParam('Integer');
                $attribute->addParam('图片名');
                $method->addAttribute($attribute);
            }
        }
        $routeMap = new Attribute($classFileConstruction->getAppropriateClassName('save' == $data['function'] ? PostMapping::class : GetMapping::class));
        $routeMap->addParam(Str::kebab($data['method']));
        $method->addAttribute($routeMap);

        if ('save' == $data['function']) {
            $middlewareAttribute = new Attribute($classFileConstruction->getAppropriateClassName(Middlewares::class));
            $middlewareAttribute->addParam([$classFileConstruction->getAppropriateClassName(ValidationMiddleware::class)]);
            $method->addAttribute($middlewareAttribute);
            $sceneAttribute = new Attribute($classFileConstruction->getAppropriateClassName(Scene::class));
            $sceneAttribute->addParam($data['method']);
            $method->addAttribute($sceneAttribute);

            $requestParam = new FunctionParam('request');
            $requestParam->setType(strtr($model, ['Models' => 'Requests']), $classFileConstruction);
            $method->addParameters($requestParam);

            $ApiAuth = $classFileConstruction->getAppropriateClassName(ApiAuth::class);

            $method->addCode($classFileConstruction->getAppropriateClassName($service) . "::aop()->{$data['method']}(\$request->validated(), $ApiAuth::get('user_id'));");
            $method->addCode('');
            $method->addCode('return Response::json()->success();');
        }else{
            $helper = $classFileConstruction->getAppropriateClassName(Helper::class);

            $method->addCode('$data = ' . $classFileConstruction->getAppropriateClassName($service) . "::aop()->" . $data['method'] . '(');
            if ('single' == $data['function']) {
                $method->addCode("    (int)$helper::request()->query('id'),");
            }else if ('multiple' == $data['function']){
                $method->addCode("    (int)$helper::request()->query('page', 1),");
                $method->addCode("    (int)$helper::request()->query('pageSize', 10),");
            }
            $method->addCode(");");
            $method->addCode("");
            $method->addCode('return Response::json()->success($data);');
        }

        $classFileConstruction->addMethods($method);

        return $method;
    }

    private function getModel(string $table): string
    {
        $modelName = Str::studly($table);
        $fullClass = '';
        ScTool::dir(BASE_PATH . '/app/Models')->each(function (EachFile $file) use ($modelName, &$fullClass){
            if (strtr($file->filename, ['.php' => '']) === $modelName){
                $fullClass = implode('\\', ['App', 'Models', ...$file->relativelyDirs]) . "\\" . $modelName;
                $file->stopEach();
            }
        });

        return $fullClass;
    }

    /**
     * @param string $filepath
     * @param string $resolveFileContent
     * @param array  $update
     *
     * @return string
     * @throws ApiMessageException
     */
    private function afterUpdateContent(string $filepath, string $resolveFileContent, array $update): string
    {
        preg_match("/^namespace\s+(.*);/m", $resolveFileContent, $namespace);
        $class = $namespace[1] . '\\' . basename($filepath, '.php');
        if (class_exists($class)) {
            $classFileConstruction = ClassFileResolve::resolve($class);
            if (empty($update['method'])) {
                throw new ApiMessageException("请输入方法名");
            }
            if (empty($update['table'])) {
                throw new ApiMessageException("请输入表名");
            }
            if (empty($update['fields'])) {
                throw new ApiMessageException("请选择查询字段");
            }

            $table = strtr($update['table'], [Model::getPrefix() => '']);
            $model = $this->getModel($table);
            $model = ClassFileConstruction::getClassName($model);

            if (str_ends_with($classFileConstruction->getName(), "Service") && $update) {
                $method = $this->makeApiService($classFileConstruction, $update, $model);
            } else if (str_ends_with($classFileConstruction->getName(), "Controller") && $update) {
                $method = $this->makeApiController($classFileConstruction, $update, $model);
            }

            $resolveFileContent = empty($method)
                ? $classFileConstruction->out()
                : preg_replace("/(.})\s*$/s", $method->out() . "$1", $resolveFileContent);
        }

        return $resolveFileContent;
    }

}